using codeparser.net.Contracts;

using System;
using System.Collections.Generic;
using System.Text;

namespace codeparser.net
{
    /// <summary>
    /// Represents a node in the syntax tree and either contains a tag or text.
    /// </summary>
    internal class Node : IToken
    {
        /// <summary>
        /// Contains the node's tokens.
        /// </summary>
        private List<IToken> _tokens;

        /// <summary>
        /// Contains the parent node.
        /// </summary>
        private Node _parent;

        /// <summary>
        /// Gets the tokens.
        /// </summary>
        /// <value>The tokens.</value>
        public List<IToken> Tokens
        {
            get
            {
                return this._tokens;
            }
        }

        /// <summary>
        /// Gets the parent.
        /// </summary>
        /// <value>The parent.</value>
        public Node Parent
        {
            get
            {
                return this._parent;
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Node"/> class.
        /// </summary>
        public Node()
        {
            this._tokens = new List<IToken>();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Node"/> class.
        /// </summary>
        /// <param name="parent">The parent.</param>
        public Node(Node parent)
            : this()
        {
            this._parent = parent;
        }

        /// <summary>
        /// Returns a parsed string that represents the current instance.
        /// </summary>
        /// <param name="parser">The parser.</param>
        /// <returns>
        /// A parsed string that represents the current instance.
        /// </returns>
        public string ToString(Parser parser)
        {
            // Initialize a string builder.
            StringBuilder text = new StringBuilder();

            // Check whether the first and the last token are tags.
            int start = 0;
            int end = this._tokens.Count;
            if (this._tokens[start] is Tag)
            {
                start++;
            }
            if (this._tokens[end - 1] is Tag)
            {
                end--;
            }

            // Iterate over all tokens.
            for (int i = start; i < end; i++)
            {
                // Get the current token.
                IToken token = this._tokens[i];

                // Decide upon the token type what to do.
                if (token is Text)
                {
                    string tempText = ((Text)token).Content;
                    foreach (ExpressionReplacement expressionReplacement in parser.Configuration.ExpressionReplacements)
                    {
                        tempText = tempText.Replace(expressionReplacement.Expression, expressionReplacement.Replacement);
                    }
                    text.Append(tempText);
                }
                else if (token is Node)
                {
                    // Get the template and the inner text.
                    Tag tag = (Tag)((Node)token).Tokens[0];
                    string template = parser.Configuration.GetTagConfiguration(tag.Name).Replacement;
                    string alternativeTemplate = parser.Configuration.GetTagConfiguration(tag.Name).AlternativeReplacement;
                    string innerText;

                    // Check whether the inner text shall be parsed. If so, recursively step into
                    // the inner text and parse it.
                    if (parser.Configuration.GetTagConfiguration(tag.Name).ParseContent)
                    {
                        innerText = ((Node)token).ToString(parser);
                    }
                    else
                    {
                        // If the inner text shall not be parsed, just get it.
                        innerText = ((Node)token).ToString(true);

                        // Check whether the inner text shall be code parsed.
                        if (parser.Configuration.GetTagConfiguration(tag.Name).ContentHighlighter != null)
                        {
                            // Get an HTML parser.
                            Wilco.SyntaxHighlighting.IParser syntaxHighlightingParser =
                                new Wilco.SyntaxHighlighting.HtmlParser();

                            // Get the highlighter for the appropriate language.
                            Wilco.SyntaxHighlighting.Register register =
                                Wilco.SyntaxHighlighting.Register.Instance;
                            Wilco.SyntaxHighlighting.HighlighterBase highlighter =
                                register.Highlighters[parser.Configuration.GetTagConfiguration(tag.Name).ContentHighlighter];
                            highlighter = highlighter.Create();
                            highlighter.Parser = syntaxHighlightingParser;
                            highlighter.ForceReset();

                            // Parse the text.
                            innerText = highlighter.Parse(innerText);

                            // Surround the parsed text by the border table.
                            StringBuilder stringBuilder = new StringBuilder();

                            stringBuilder.Append("<table style=\"width:100%;\">");
                            stringBuilder.Append("<tr><td colspan=\"2\" style=\"font-weight:bold;\">");
                            stringBuilder.Append(highlighter.FullName);
                            stringBuilder.Append("</td></tr>");
                            stringBuilder.Append("<tr>");
                            stringBuilder.Append("<td style=\"text-align:right;vertical-align:top;width:1%;\"><pre>");
                            for (int lineNumber = 1; lineNumber <= innerText.Replace("\r", String.Empty).Split('\n').Length; lineNumber++)
                            {
                                stringBuilder.Append("<span style=\"border-right:solid 1px #000000;padding-right:2px;\">");
                                stringBuilder.Append((lineNumber).ToString());
                                stringBuilder.Append("</span>");
                                stringBuilder.Append(Environment.NewLine);
                            }
                            stringBuilder.Append("</pre></td>");
                            stringBuilder.Append("<td style=\"vertical-align:top;width:99%;\"><pre>");
                            stringBuilder.Append(innerText);
                            stringBuilder.Append("</pre></td>");
                            stringBuilder.Append("</tr>");
                            stringBuilder.Append("</table>");

                            innerText = stringBuilder.ToString();
                        }
                    }

                    // Combine the template, the inner text and optional parameters.
                    string outerText = "";
                    if (tag.Parameters != null)
                    {
                        object[] parameters = new object[tag.Parameters.Count + 1];
                        parameters[0] = innerText;
                        for (int j = 1; j < parameters.Length; j++)
                        {
                            parameters[j] = tag.Parameters[j - 1].Value;
                        }

                        // Try to apply the primary replacement. If that fails, fall back and
                        // try it with the alternate replacement.
                        try
                        {
                            outerText = String.Format(template, parameters);
                        }
                        catch(FormatException)
                        {
                            outerText = String.Format(alternativeTemplate, parameters);
                        }
                    }
                    else
                    {
                        // Try to apply the primary replacement. If that fails, fall back and
                        // try it with the alternate replacement.
                        try
                        {
                            outerText = String.Format(template, innerText);
                        }
                        catch (FormatException)
                        {
                            outerText = String.Format(alternativeTemplate, innerText);
                        }
                    }

                    // Add them to the return text.
                    text.Append(outerText);
                }
            }

            // Return the text to the caller.
            return text.ToString();
        }

        /// <summary>
        /// Returns a string that represents the current instance without parsing it.
        /// </summary>
        /// <param name="ignoreContainerTags">If set to <c>true</c>, the container tags are ignored.</param>
        /// <returns>
        /// A string that represents the current instance.
        /// </returns>
        private string ToString(bool ignoreContainerTags)
        {
            // Initialize a string builder.
            StringBuilder text = new StringBuilder();

            // Get the start and end.
            int start = ignoreContainerTags ? 1 : 0;
            int end = ignoreContainerTags ? this._tokens.Count - 1 : this._tokens.Count;

            // Iterate over all tokens.
            for (int i = start; i < end; i++)
            {
                // Get the current token.
                IToken token = this._tokens[i];

                // Decide upon the token type what to do.
                if (token is Text)
                {
                    if (((Text)token).IsSpecialCharacter)
                    {
                        text.Append(((Text)token).Content);
                    }
                    text.Append(((Text)token).Content);
                }
                else if (token is Tag)
                {
                    Tag tag = (Tag)token;
                    text.Append(tag.OriginalString);
                }
                else if (token is Node)
                {
                    text.Append(((Node)token).ToString(false));
                }
            }

            // Return the text to the caller.
            return text.ToString();
        }
    }
}